#include <iostream>
#include <regex>
#include <cassert>
#include "actor.hpp"
#include "config.hpp"
#include "util.hpp"
#include "game.hpp"
#include "display_str.hpp"

Actor::Actor(void)
{
}

Actor::~Actor(void)
{
}

void Actor::InitActor(Game *g)
{
    m_name = '\0';
    m_fund = g->m_default_fund;
    m_credit = 0;
    m_loc = 0;
    m_barrier = 0;
    m_robot = 0;
    m_god = 0;
    m_inMagic = 0;
    m_bankruptcy = false;
    m_game = g;
    m_sell = false;
    m_start = false;
}

bool Actor::QueryBuyEmpty(Land &land)
{
    static std::regex pat_yn("\\s*(y|n)\\b\\s*(#.*)?$", std::regex::icase);
    int payMoney = land.GetEmptyLandPrice();
    if (m_fund < payMoney)
        return false;
    else
    {
        std::string line;
        while (true)
        {
            m_game->SetInputTip(QueryBuyEmptyTip);
            std::getline(std::cin, line);
            line = to_lower(line);
            std::smatch result;
            if (std::regex_match(line, result, pat_yn))
            {
                if (result.str(1) == "y")
                {
                    m_game->SetTip(GetCName(m_name) + BuyEmptyY);
                    m_game->ClearInputTip();
                    m_fund -= payMoney;
                    return true;
                }
                else if (result.str(1) == "n")
                {
                    m_game->ClearInputTip();
                    return false;
                }
            }
        }
    }
}

bool Actor::QueryUpdateBuliding(Land &land)
{
    static std::regex pat_yn("\\s*(y|n)\\b\\s*(#.*)?$", std::regex::icase);
    int payMoney = land.GetEmptyLandPrice();
    if (m_fund < payMoney)
        return false;
    else
    {
        std::string line;
        while (true)
        {
            m_game->SetInputTip(QueryUpdateBulidingTip);
            std::getline(std::cin, line);
            line = to_lower(line);
            std::smatch result;
            if (std::regex_match(line, result, pat_yn))
            {
                if (result.str(1) == "y")
                {
                    m_game->SetTip(GetCName(m_name) + UpdateBulidingY);
                    m_game->ClearInputTip();
                    m_fund -= payMoney;
                    return true;
                }
                else if (result.str(1) == "n")
                {
                    m_game->ClearInputTip();
                    return false;
                }
            }
        }
    }
}

int Actor::PayRent(Land &land)
{
    int payMoney = land.GetLandPrice() / 2;
    if (m_fund < payMoney)
    {
        m_game->SetTip(GetCName(m_name) + BankruptcyStr);
        m_bankruptcy = true;
        return 0;
    }
    else
    {
        m_fund -= payMoney;
        return payMoney;
    }
}

void Actor::GetMoney(int num)
{
    assert(num >= 0);
    m_fund += num;
}

int Actor::MagicHouse()
{
    static std::regex pat_01234("\\s*([0-4])\\s*(#.*)?$", std::regex::icase);
    std::string line;
    while (true)
    {
        m_game->SetInputTip(MagicHouseTip);
        std::getline(std::cin, line);
        line = to_lower(line);
        std::smatch result;
        if (std::regex_match(line, result, pat_01234))
        {
            m_game->ClearInputTip();
            auto ch = GetPlayerName(result.str(1)[0]);
            if (ch == '\0')
                return -1;
            if (m_game->m_name2idx.find(ch) == m_game->m_name2idx.end())
                continue;
            size_t idx = 0;
            for (; idx < 4; ++idx)
                if (m_game->m_actor[idx].m_name == ch)
                    break;
            if (m_game->m_actor[idx].m_bankruptcy)
            {
                std::cerr << "cmd error" << std::endl;
                m_game->SetTip(CmdErrorStr);
                continue;
            }
            m_game->SetTip(GetCName(m_name) + FrameStr + GetCName(ch));
            return idx;
        }
        else
        {
            std::cerr << "cmd error" << std::endl;
            m_game->SetTip(CmdErrorStr);
        }
    }
}

void Actor::Enchanted(void)
{
    m_inMagic += STOP_DAY;
}

void Actor::ToolHouse(void)
{
    static std::regex pat_12f("\\s*([12f])\\s*(#.*)?$", std::regex::icase);
    if (m_barrier + m_robot >= 10)
    {
        m_game->SetTip(GetCName(m_name) + BagFullStr + ExitToolHouseTip);
        return;
    }
    std::string line;
    while (true)
    {
        if (m_credit < std::min(PROP_ROBOT, PROP_BARRIER))
        {
            m_game->SetTip(GetCName(m_name) + CreditNotEnoughStr + ExitToolHouseTip);
            break;
        }
        if (m_barrier + m_robot >= 10)
        {
            m_game->SetTip(GetCName(m_name) + BagFullStr + ExitToolHouseTip);
            break;
        }
        m_game->SetInputTip(ToolHouseTip);
        std::getline(std::cin, line);
        m_game->ClearInputTip();
        line = to_lower(line);
        std::smatch result;
        if (std::regex_match(line, result, pat_12f))
        {
            auto ch = result.str(1)[0];
            if (ch == 'f')
                break;
            else if (ch == '1')
            {
                if (m_credit < PROP_BARRIER)
                {
                    m_game->SetTip(GetCName(m_name) + CreditNotEnoughStr);
                    continue;
                }
                ++m_barrier;
                m_credit -= PROP_BARRIER;
                m_game->SetTip(GetCName(m_name) + BuyBarrierStr);
            }
            else if (ch == '2')
            {
                if (m_credit < PROP_ROBOT)
                {
                    m_game->SetTip(GetCName(m_name) + CreditNotEnoughStr);
                    continue;
                }
                ++m_robot;
                m_credit -= PROP_ROBOT;
                m_game->SetTip(GetCName(m_name) + BuyRobotStr);
            }
        }
        else
        {
            m_game->SetTip(CmdErrorStr);
            std::cerr << "cmd error" << std::endl;
        }
    }
}

void Actor::GiftHouse(void)
{
    static std::regex pat_123("\\s*([123])\\s*(#.*)?$", std::regex::icase);
    std::stringstream ss;
    std::string line;
    m_game->SetInputTip(GiftHouseTip);
    std::getline(std::cin, line);
    m_game->ClearInputTip();
    std::smatch result;
    if (std::regex_match(line, result, pat_123))
    {
        auto ch = result.str(1)[0];
        if (ch == '1')
        {
            m_fund += GIFT_MONEY;
            ss << GetCName(m_name) << GetFundStr << GIFT_MONEY;
            m_game->SetTip(ss.str());
            return;
        }
        else if (ch == '2')
        {
            m_credit += GIFT_CREDIT;
            ss << GetCName(m_name) << GetCreditStr << GIFT_CREDIT;
            m_game->SetTip(ss.str());
            return;
        }
        else if (ch == '3')
        {
            m_god = m_god + GIFT_GOD + (m_god == 0);
            ss << GetCName(m_name) << GetGodStr << GIFT_GOD;
            m_game->SetTip(ss.str());
            return;
        }
    }
    m_game->SetTip(CmdErrorStr);
}

bool Actor::isBankruptcy(void)
{
    return m_bankruptcy;
}

int Actor::UserAction(void)
{
    static std::regex pat("\\s*(?:roll|sell\\s+\\d+|block\\s+-?\\d+|robot|query|step\\s+\\d+)\\s*(#.*)?$", std::regex::icase);
    m_game->UpdateDisplay();
    std::string cmd;
    while (true)
    {
        std::getline(std::cin, cmd);
        cmd = to_lower(cmd);
        auto res = m_game->m_cfunc.ParaseAndCall(cmd);
        if (res.length() > 0)
        {
            m_game->UpdateDisplay();
            if (m_game->m_actor[m_game->m_current_user].m_name != m_name)
                return -2;
            if (m_inMagic > 0)
                return -1;
            continue;
        }
        std::stringstream ss(cmd);
        std::string ctr;
        ss >> ctr;
        if (!std::regex_match(cmd, pat))
        {
            std::cerr << "cmd error: " + cmd << std::endl;
            m_game->SetTip(CmdErrorStr);
            continue;
        }
        if (m_game->m_user_num == 0)
        {
            std::cerr << "cmd error: set user first" << std::endl;
            m_game->SetTip(CmdErrorStr + SetUserFirstStr);
            continue;
        }
        if (ctr == "roll")
        {
            std::stringstream sout;
            auto ret = rand() % 6 + 1;
            sout << GetCName(m_name) << RollStr << ret;
            m_game->SetTip(sout.str());
            return ret;
        }
        else if (ctr == "sell" && !m_sell)
        {
            int loc;
            ss >> loc;
            auto res = m_game->SellLand(m_name, loc);
            if (!res)
            {
                std::cerr << "cmd error: " + cmd << std::endl;
                m_game->SetTip(CmdErrorStr);
            }
            else
            {
                m_sell = true;
                m_game->SetTip(GetCName(m_name) + SellStr);
            }
        }
        else if (ctr == "block")
        {
            if (m_barrier < 1)
            {
                m_game->SetTip(GetCName(m_name) + NoBarrierStr);
                continue;
            }
            int loc;
            ss >> loc;
            if (loc < -10 || loc > 10)
            {
                m_game->SetTip(GetCName(m_name) + CantPlaceBarrier);
                continue;
            }
            auto res = m_game->PlaceBarrier((m_loc + loc + 70) % 70);
            if (!res)
            {
                m_game->SetTip(GetCName(m_name) + CantPlaceBarrier);
                std::cerr << "cmd error: " + cmd << std::endl;
            }
            else
                --m_barrier;
            m_game->UpdateDisplay();
        }
        else if (ctr == "robot")
        {
            if (m_robot < 1)
            {
                m_game->SetTip(GetCName(m_name) + NoRobotStr);
                continue;
            }
            auto res = m_game->PlaceRobot(m_loc);
            if (!res)
            {
                m_game->SetTip(GetCName(m_name) + CantPlaceRobot);
                std::cerr << "cmd error: " + cmd << std::endl;
            }
            else
                --m_robot;
            m_game->UpdateDisplay();
        }
        else if (ctr == "query")
        {
            std::stringstream sout;
            std::stringstream temps;
            sout << GetCName(m_name) << std::endl
                 << FundStr << ": " << m_fund << "  " << CreditStr << ": " << m_credit << std::endl;
            sout << BarrierStr << ": " << m_barrier << "  " << RobotStr << ": " << m_robot << "  " << GodStr << ": " << m_god;
            sout << std::endl
                 << QueryTitleStr << std::endl;

            for (size_t i = 1; i < 28; ++i)
                if (m_game->m_land[i].owner == m_name)
                    sout << i << "\t" << LandTypeStr << "1\t" << GetLevelStr(m_game->m_land[i].level) << std::endl;
            for (size_t i = 29; i < 35; ++i)
                if (m_game->m_land[i].owner == m_name)
                    sout << i << "\t" << LandTypeStr << "2\t" << GetLevelStr(m_game->m_land[i].level) << std::endl;
            for (size_t i = 36; i < 63; ++i)
                if (m_game->m_land[i].owner == m_name)
                    sout << i << "\t" << LandTypeStr << "3\t" << GetLevelStr(m_game->m_land[i].level) << std::endl;

            m_game->FullScreenTip(sout.str());
        }
        else if (ctr == "step")
        {
            int n;
            ss >> n;
            if (n < 0)
            {
                std::cerr << "cmd error: " + cmd << std::endl;
                m_game->SetTip(CmdErrorStr);
                continue;
            }
            return n;
        }
        m_game->UpdateDisplay();
    }

    return 1;
}

void Actor::Mine(int loc)
{
    static int credits[] = {60, 80, 40, 100, 80, 20};
    if (loc < MINE_LOCATION_MIN || loc > MINE_LOCATION_MAX)
        return;
    m_credit += credits[loc - MINE_LOCATION_MIN];
    std::stringstream ss;
    ss << GetCName(m_name) << GetCreditStr << credits[loc - MINE_LOCATION_MIN];
    m_game->SetTip(ss.str());
}